package com.letoken.platform.app.consumer;

import cn.hutool.extra.spring.SpringUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.letoken.platform.repo.performance.TeamPerformanceService;
import com.letoken.platform.app.configuration.PancakeSwapLPQuery;
import com.letoken.platform.app.service.AppUserWalletService;
import com.letoken.platform.pub.constant.ConstantsValue;
import com.letoken.platform.pub.enums.LetokenTokenTypeEnum;
import com.letoken.platform.pub.enums.TeamPerformanceTypeEnum;
import com.letoken.platform.pub.enums.TokenTransactionStatusEnum;
import com.letoken.platform.pub.enums.TokenTransactionTypeEnum;
import com.letoken.platform.pub.util.LocalDateTool;
import com.letoken.platform.pub.util.RedisIdeTool;
import com.letoken.platform.repo.app.dao.*;
import com.letoken.platform.repo.app.po.*;
import com.letoken.platform.repo.mall.po.AppLpLockHistory;
import com.letoken.platform.repo.utlis.LocalBlockUtils;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.CustomExchange;
import org.springframework.amqp.core.Queue;
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.messaging.handler.annotation.Payload;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.RestController;
import software.amazon.awssdk.services.sqs.endpoints.internal.Value;

import javax.annotation.Resource;
import java.io.Serializable;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.LocalDateTime;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;

@Slf4j
@Component
@RabbitListener()
@RestController
public class RechargeConsumer {
    private final static String exchange = "letoken.wallet_center";
    private final static String routingKey = "topic.#";
    private final static String queue = "topic.recharge.new";
    @Bean
    public Queue grabQueue() {
        return new Queue("topic.#");
    }

    @Bean
    public CustomExchange rechargeExchange() {
        Map<String, Object> args = new HashMap<>(1);
        return new CustomExchange(exchange, "topic", true, false, args);
    }

    @Bean
    public Binding bindingNotify(@Qualifier("grabQueue") Queue queue,
                                 @Qualifier("rechargeExchange") CustomExchange customExchange) {
        return BindingBuilder.bind(queue).to(customExchange).with(routingKey).noargs();
    }

    @Autowired
    AppUserRechargeMapper appUserRechargeMapper;
    @Autowired
    AppUserWalletMapper appUserWalletMapper;
    @Autowired
    AppUserAssetsHistoryMapper appUserAssetsHistoryMapper;
    @Autowired
    TokenMapper tokenMapper;
    @Autowired
    RedisIdeTool redisIdeTool;
    @Autowired
    RabbitTemplate rabbitTemplate;

    @Resource
    AppUserExchangeInfoMapper appUserExchangeInfoMapper;
    @Autowired
    AppLpLockHistoryMapper appLpLockHistoryMapper;


    private static Integer LOCK_DAYS = 180;

    @Resource
    private TeamPerformanceService teamPerformanceService;

    @RabbitListener(queues = queue)
    @RabbitHandler
    public void rechargeConfirm(@Payload String message) {
        log.info("收到充值确认消息:{}",message);
        if (!StringUtils.hasLength(message)) {
            return;
        }
        RechargeConfirmDto req = JSONObject.parseObject(message, RechargeConfirmDto.class);
        try {
            if (!req.check() || !redisIdeTool.checkAndSetStr(RedisIdeTool.BLOCK_TX_STR,req.getTxId())){
                // 拒绝消费 TODO 消息消费要改成手动消费
                throw new RuntimeException(String.format("幂等校验未通过 %s",req));
            }
            // 订单号和txid 是唯一索引了
            if ( null != appUserRechargeMapper.checkoutTxIdOrSn(req.getTxId(),req.getSeqNo()) ){
                log.info("拒绝消费 已经存在记录txId或seqNo重复 一般认为已经成功 {}",req);
                confirmAck(req);
                return;
            }

            Integer appUserWalletId ;

            if(1 == req.getCoinId()){
                req.setCoinId(2);
            }

            if (null == (appUserWalletId = appUserWalletMapper.checkoutTokenIdAndUserId(req.getCoinId(),req.getUserId()))){
                log.info("拒绝消费 没有匹配到用户和币种ID的钱包记录 {}",req);
                return;
            }
            req.setAppUserWalletId(appUserWalletId);
            RechargeConsumer rc = SpringUtil.getBean(RechargeConsumer.class);
            rc.dro(req);

        } catch (Exception e) {
            log.error("充值确认出现异常!",e);
            // todo 如果出现异常，需要通知开发者
            throw new RuntimeException(String.format("充值确认出现异常 %s",req));
        } finally {
            redisIdeTool.delStr(RedisIdeTool.BLOCK_TX_STR,req.getTxId());
        }
    }


    @Transactional(rollbackFor = Exception.class)
    public void dro(RechargeConfirmDto req){
        AppUserWallet appUserWallet = appUserWalletMapper.selectForUpdate(req.getAppUserWalletId());
        if (appUserWallet.getAvailableBalance().compareTo(BigDecimal.ZERO) < 0){
            log.info("用户 {} 钱包记录 {} 可用金额小于0，钱包账户异常");
            throw new RuntimeException("金额小于0，异常记录");
        }

        Token token = tokenMapper.selectById(req.getCoinId());

        AppUserRecharge record = new AppUserRecharge();
        record.setTxid(req.getTxId());
        record.setUserId(req.getUserId());
        record.setAmount(req.getAmount());
        record.setTokenId(req.getCoinId());
        record.setCreateTime(LocalDateTime.now());
        record.setStatus(TokenTransactionStatusEnum.COMPLETED.getValue());
        record.setConfirms(req.getBlockNumber());
        record.setSource(1); //链上充值
        record.setSn(req.getSeqNo());
        record.setLeAddress(req.getToAddr());
        appUserRechargeMapper.insert(record);

        if (LetokenTokenTypeEnum.LP.getId().equals(token.getId())){
            LocalDateTime now = LocalDateTime.now();
            AppLpLockHistory allh = new AppLpLockHistory();
            allh.setSeqNo(req.seqNo);
            allh.setType(ConstantsValue.I_0);
            allh.setUserId(req.getUserId());
            allh.setTokenId(token.getId());
            allh.setAmount(req.getAmount());
            allh.setBalance(appUserWallet.getReleasedBalance());
            allh.setCreateTime(now);
            allh.setUnlockTime(LocalDateTool.plusDayFull(now,LOCK_DAYS));
            appLpLockHistoryMapper.insert(allh);

            AppUserWallet newWallet = new AppUserWallet();
            newWallet.setId(appUserWallet.getId());
            newWallet.setReleasedBalance(appUserWallet.getReleasedBalance().add(req.getAmount()));
            appUserWalletMapper.updateById(newWallet);

            try {
                addCAP(req.getUserId(),req.getAmount(),req.seqNo);
            } catch (Exception e) {
                log.info("空头奖励失败"+req.getUserId()+"   错误信息"+e.getMessage());
            }

        }else {
            appUserWalletMapper.updateBalance(appUserWallet.getId(),req.getAmount());
            if(req.getCoinId().equals(4)){
                addExchangeAmount(record.getUserId(),req.getAmount());
            }
        }

        AppUserAssetsHistory data = new AppUserAssetsHistory();
        data.setType(TokenTransactionTypeEnum.RECHARGE.getValue());
        data.setUserId(record.getUserId());
        data.setTokenId(record.getTokenId());
        data.setTokenCurrency(token.getTokenCurrency());
        data.setTxid(record.getTxid());
        data.setSn(record.getSn());
        data.setStatus(record.getStatus());
        data.setCreateTime(LocalDateTime.now());
        data.setAmount(record.getAmount());
        data.setFromUserName("le" + record.getUserId().toString());
        data.setFromAddress(req.getFromAddr());
        data.setToAddress(req.getToAddr());
        data.setBalance(appUserWallet.getAvailableBalance().add(req.getAmount()));
        appUserAssetsHistoryMapper.insert(data);

        if(LetokenTokenTypeEnum.CAP.getId().equals(record.getTokenId())){
            teamPerformanceService.add(TeamPerformanceTypeEnum.CapRecharge, req.userId, req.amount);
        } else if(LetokenTokenTypeEnum.LET.getId().equals(record.getTokenId())) {
            teamPerformanceService.add(TeamPerformanceTypeEnum.UsdtRecharge, req.userId, req.amount);
        }
        confirmAck(req);
    }


    private void confirmAck(RechargeConfirmDto req){
        LocalBlockUtils.BaseResp resp = LocalBlockUtils.rechargeConfirm(LocalBlockUtils.RechargeConfirmAck
                .builder()
                .id(req.getId())
                .userId(req.userId)
                .amount(req.getAmount())
                .txId(req.getTxId())
                .build());
        if (!resp.isSuccess()){
            throw new RuntimeException(String.format("充值确认出现异常!",resp));
        }
    }





    @Data
    @AllArgsConstructor
    public static class RechargeConfirmDto implements Serializable {
        private Integer id;
        private String seqNo;
        private Integer userId;
        private Integer coinId;
        private String txId;
        private String fromAddr;
        private String toAddr;
        private Integer status;
        private Integer blockNumber;
        // 区块高度
        private String blockHash;
        private BigDecimal amount;
        private LocalDateTime createTime;
        private LocalDateTime updateTime;




        private Integer appUserWalletId;


        public Boolean check(){
            if (StringUtils.hasLength(seqNo)
                    && (userId != null && userId > 0)
                    && (coinId != null && coinId > 0)
                    && StringUtils.hasLength(txId)
                    && StringUtils.hasLength(fromAddr)
                    && StringUtils.hasLength(toAddr)
                    && (amount != null && amount.compareTo(BigDecimal.ZERO) > 0)
            ){
                amount = amount.setScale(4, RoundingMode.FLOOR);
                return true;
            }
            log.info("参数错误:{}",this);
            return false;
        }


        @Override
        public String toString(){
            return JSON.toJSONString(this);
        }
    }


    public void addExchangeAmount(Integer userId,BigDecimal amount){
        QueryWrapper<AppUserExchangeInfo> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("user_id",userId);
        AppUserExchangeInfo appUserExchangeInfo = appUserExchangeInfoMapper.selectOne(queryWrapper);
        if(Objects.isNull(appUserExchangeInfo)){
            appUserExchangeInfo = new AppUserExchangeInfo();
            appUserExchangeInfo.setUserId(userId);
            appUserExchangeInfo.setAmount(amount);
            appUserExchangeInfo.setCreateTime(LocalDateTime.now());
            appUserExchangeInfo.insert();
        }else {
            appUserExchangeInfo.setAmount(appUserExchangeInfo.getAmount().add(amount));
            appUserExchangeInfo.updateById();
        }
    }

    @Autowired
    private AppUserWalletService appUserWalletService;
    private BigDecimal userRate = new BigDecimal("0.1");
    private BigDecimal upRate = new BigDecimal("0.03");

    private BigDecimal amounts = new BigDecimal("1000");
    @Autowired
    private AppFundInfoMapper appFundInfoMapper;

    @Transactional
    public void addCAP(Integer userId, BigDecimal amount, String seq) throws Exception {
        PancakeSwapLPQuery.Price price = PancakeSwapLPQuery.getPrice();
        BigDecimal lpPrice = amount.multiply(price.getLp());
        if(lpPrice.compareTo(amounts)<0)return;
        BigDecimal bigDecimal = lpPrice.divide(price.getCap(),8,RoundingMode.DOWN);
        appUserWalletService.updateWallet(LetokenTokenTypeEnum.CAP.getId(),userId,bigDecimal.multiply(userRate),TokenTransactionTypeEnum.CAP_airdrop.getValue(),seq);

        Integer upId = appFundInfoMapper.getLevel(userId);
        if(upId !=null && !upId.equals(0)){
            appUserWalletService.updateWallet(LetokenTokenTypeEnum.CAP.getId(),upId,bigDecimal.multiply(upRate),TokenTransactionTypeEnum.CAP_airdrop_Community.getValue(),seq);
        }
    }
}
